package com.example.seckill.auth.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.seckill.auth.dao.RedisDao;
import com.example.seckill.auth.dto.Exposer;
import com.example.seckill.auth.dto.SeckillExecution;
import com.example.seckill.auth.entity.Seckill;
import com.example.seckill.auth.entity.SuccessSeckilled;
import com.example.seckill.auth.exception.RepeatKillException;
import com.example.seckill.auth.exception.SeckillCloseException;
import com.example.seckill.auth.exception.SeckillException;
import com.example.seckill.auth.mapper.SeckillMapper;
import com.example.seckill.auth.mapper.SuccessSeckilledMapper;
import com.example.seckill.auth.service.SeckillService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.seckill.auth.service.SuccessSeckilledService;
import com.example.seckill.system.tools.SeckillEnum;
import com.example.seckill.system.util.Md5;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * 秒杀库存表 服务实现类
 * </p>
 *
 * @author hl
 * @since 2020-06-11
 */
@Service
public class SeckillServiceImpl extends ServiceImpl<SeckillMapper, Seckill> implements SeckillService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private final String key = "asdasdasdasdasd&&%%^&asdzxczxcaqwdas";

    @Autowired
    private SuccessSeckilledService successSeckilledService;

    @Autowired
    private RedisDao redisDao;

    @Autowired
    private SuccessSeckilledMapper successSeckilledMapper;

    @Autowired
    private SeckillMapper mapper;

    @Override
    public int reduceNumber(long seckillId, Timestamp killTime) {
        Seckill seckill = getById(seckillId);
        QueryWrapper<Seckill> queryWrapper = new QueryWrapper<>();
        /**
         * 库存大于0
         */
        queryWrapper.gt("number", 0);
        queryWrapper.eq("seckill_id", seckill.getSeckillId());
        /**
         * 已经开始的秒杀
         * le小于等于
         */
        queryWrapper.le("start_time", killTime);
        /**
         * 未结束的秒杀
         * ge大于等于
         */
        queryWrapper.ge("end_time", killTime);
        seckill.setNumber(seckill.getNumber() - 1);
        if (update(seckill, queryWrapper)) {
            return 1;
        } else {
            return 0;
        }
    }

    @Override
    public Exposer exportSeckillUrl(Long seckillId) {
        /**
         * 访问redis
         */
        Seckill seckill = redisDao.getSeckill(seckillId);
        if (seckill == null) {
            /**
             * redis不存在数据，访问数据库
             */
            seckill = getById(seckillId);
            if (seckill == null) {
                /**
                 * 数据库不存在
                 */
                return new Exposer(false, seckillId);
            } else {
                /**
                 * 数据库存在，redis
                 */
                redisDao.putSeckill(seckill);
            }
        }

        Timestamp start = seckill.getStartTime();
        Timestamp end = seckill.getEndTime();
        long now = System.currentTimeMillis();
        if (now < start.getTime() || now > end.getTime()) {
            return new Exposer(false, seckillId, now, start.getTime(), end.getTime());
        }

        return new Exposer(true, getMd5(seckillId), seckillId);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public SeckillExecution executeSeckill(Long seckillId, Long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {
        try {
            if (md5 == null || !md5.equals(getMd5(seckillId))) {
                throw new SeckillException("seckill data rewrite");
            }
            /**
             * 先执行insert，根据结果判断是否执行update，缩短行级锁时间
             */
            SuccessSeckilled successSeckilled = new SuccessSeckilled();
            successSeckilled.setSeckillId(seckillId);
            successSeckilled.setUserPhone(userPhone);
            int insertCount = successSeckilledMapper.insertIgnore(successSeckilled);
            if(insertCount>0){
                int updateCount = reduceNumber(seckillId, new Timestamp(System.currentTimeMillis()));
                if (updateCount <= 0) {
                    throw new SeckillCloseException("seckill is close");
                } else {
                    QueryWrapper<SuccessSeckilled> queryWrapper = new QueryWrapper<>();
                    queryWrapper.eq("seckill_id", seckillId).eq("user_phone", userPhone);
                    SuccessSeckilled seckilled = successSeckilledService.getOne(queryWrapper);
                    return new SeckillExecution(seckillId, SeckillEnum.SUCCESS, seckilled);
                }
            } else {
                throw new RepeatKillException("seckill repeat");
            }
        } catch (SeckillCloseException e1) {
            throw e1;
        } catch (RepeatKillException e2) {
            throw e2;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new SeckillException("seckill inner error:" + e.getMessage());
        }
    }

    @Override
    public SeckillExecution executeSeckillProcedure(Long seckillId, Long userPhone, String md5) {
        if (md5 == null || !md5.equals(getMd5(seckillId))) {
            return new SeckillExecution(seckillId,SeckillEnum.DATA_REWRITE);
        }
        Date killTime = new Date();
        Map<String,Object> map = new HashMap<>();
        map.put("seckillId",seckillId);
        map.put("phone",userPhone);
        map.put("state",0);
        map.put("killTime",killTime);
        map.put("result",-3);
        try {
            mapper.killByProcedure(map);
            int result = MapUtils.getInteger(map,"result",-2);
            if(result == 0){
                QueryWrapper<SuccessSeckilled> queryWrapper = new QueryWrapper<>();
                queryWrapper.eq("seckill_id", seckillId).eq("user_phone", userPhone);
                SuccessSeckilled seckilled = successSeckilledService.getOne(queryWrapper);
                return new SeckillExecution(seckillId, SeckillEnum.SUCCESS, seckilled);
            } else {
                return new SeckillExecution(seckillId, SeckillEnum.getseckillEnum(result));
            }
        } catch (Exception e) {
            logger.error(e.getMessage(),e);
            return new SeckillExecution(seckillId, SeckillEnum.INNER_ERROR);        }
    }

    private String getMd5(long seckillId) {
        String base = seckillId + "/" + key;
        String md5 = Md5.getMD5(base);
        return md5;
    }

}
